//////////////////////////////////////////////////////////////////////////////////////////
// Paging Manager class for EGTB Reader
//////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "Common.h"

namespace DbReader
{
	#define EMPTY_LRU		(-1)		// empty LRU record

	// compression types
	#define CPR_UNKNOWN			(-1)
	#define	CPR_JUST_COPY		0
	#define	CPR_RLE				1
	#define CPR_LZMA			2
	#define	CPR_LZF				3
	#define	CPR_ZIP				4

	// EGTB File record class
	struct EGTBFile
	{
		FILE* fp;						// EGTB file handle
		uint64 nPageDataOffset;			// offset from the beginning of paging subsystem records
		uint64 nGlobalBlockNumber;		// returned value of global block number
	};


	
	#pragma pack(push, 1) // make alignment to one byte

	// header in the EGTB file containing data for decompression
	struct FilePagingHeader
	{
	public:
		int32 iBlockCount;			// the number of blocks
		int32 iPageSize;			// the decompressed block/page size
		int64 iIndexOffset;			// the offset of recorded indexes from the beginning of the file
		int64 iDataOffset;			// the offset of compressed data from the beginning of the file
	};

	#pragma pack(pop)

	// file paging record
	class FilePagingRecord
	{
	public:
		FILE* fp;						// EGTB file handle
		uint64 nCompressedDataOffset;	// offset of the beginning of compressed data
		uint32 nStartingBlock;			// block, where the file starts
		uint32 nBlockCount;				// the total block count
		uint64* pBlockOffsets;			// map of block numbers to the file offset relative to the beginning

		// destructor
		~FilePagingRecord()
		{
			if(pBlockOffsets != NULL)
			{
				delete[] pBlockOffsets;
				pBlockOffsets = NULL;
			}
		}
	};

	// LRU double linked-list struct
	struct LRUListItem
	{
		int32 nBlockNumber;		// block number
		int32 nPrevItem;		// previous item index
		int32 nNextItem;		// next item index
	};

	// The class responsible for keeping pages of EGTB compressed blocks
	class PagingManager
	{
	public:
		// constructor / destructor
		PagingManager(int32 iCacheSizeMB);
		~PagingManager();

		// Public API methods
		/////////////////////////////////////////

		// Initialization of paging subsystem
		bool Initialize(int fileCount, EGTBFile* pFiles, int32 nCompressionType);

		// returns the requested block
		uint8* GetBlock(int fileIndex, int nGlobalBlockNumber);

		// returns page/block size
		int32 inline GetBlockSize() { return m_nPageSize; }

		// returns the current index in the block
		uint32 inline GetCurrentIndexInBlock() { return m_nIndexInBlock; }

		// returns the current block number being compressed
		uint32 inline GetCurrentBlockNumber() { return m_nCurrentBlockNumber; }

		// return the number of blocks in the cache
		uint32 inline GetCacheBlockCount() { return m_nCacheBlockCount; }

		// return the number of blocks across all EGTB files
		uint64 inline GetTotalBlockCount() { return m_nBlockCount; }

		// initializes PagingManager into the compression mode
		void InitializeCompressionMode(FILE* fp, int32 nCompressionType, uint32 nTotalBlockCount, int32 nPageSize);

		// writes one byte to the stream of compressed DB, either WDL or DTM
		// invokes compression when the buffer is empty
		void WriteByte(uint8 value);

		// finalizes the compression, that is, saves the last block, updates indexes 
		// and saves them
		void FinalizeCompression();	

	private:

		// compresses and writes the page
		void CompressAndWritePage();

		// loads the EGTB file header and initializes the data structures
		bool LoadFileHeaderInfo(EGTBFile* file, int nFileIndex);

		// initializes data cache and sets initial values of some variables
		void InitCache();

		// initializes LRU double-linked list of blocks
		void InitLRU();

		// moves the LRU item to the top 
		// nIndex:  is the index of block in the cache
		void MoveLRUItemToHead(int nIndex);

		// find the least recently used block
		int32 FindLRUBlock();

		// compresses the block - returns the size
		int32 CompressBlock(uint8* pSrc, uint8* pDst);

		// decompresses block
		void UncompressBlock(uint8* pSrc, uint8* pDst, size_t compressedSize);

		// reads block with the given local file block number
		// returns the block size
		int32 ReadBlock(int32 nFileIndex, int32 nLocalBlockNumber, uint8* pOutput);


		// Internal variables
		//////////////////////////////////////////////////////////////////////////////////////

		// Compression writing mode variables
		///////////////////////////////////////////////////////////////////

		uint32 m_nIndexInBlock;			// the current index in the the blocked being written and compressed
		uint32 m_nCurrentBlockNumber;	// the number of block being currently written and compressed
		FILE* m_pCompressedFile;		// the file where compressed data are written
		uint32 m_nTotalBlockCount;		// total block count in the compressed file
		uint64* m_pFileBlockOffsets;	// offsets of blocks in the compressed file
		uint64 m_nIndexFileOffset;		// variable for saving the file offset  of indexes
		uint8* m_pCompressBuffer;		// buffer where data is being compressed

		// Reading mode variables
		///////////////////////////////////////////////////////////////////
		
		int32 m_nCompressionType;	// EGTB compression type
		int32 m_iCacheSizeMB;		// the suggested size in MB of the block cache
		int32 m_nPageSize;			// one page size
		int64 m_nBlockCount;		// the block count accross all EGTB files
		int32 m_nCacheBlockCount;	// the count of blocks in the cache

		uint8* m_pDataCache;		// pointer to the cache
		uint8** m_pBlockPointers;	// array mapping blockIndex to the data pointer in the data cache
		uint8* m_pTmpBlock;			// temporary block used for compression and decompression

		int32 m_nFileCount;			// number of EGTB files
		FilePagingRecord* m_pFiles;	// the file record array

		LRUListItem* m_pLRU;		// LRU double linked-list
		int32 m_nHead;				// LRU list head
		int32 m_nTail;				// LRU list tail
		int32 m_nLast;				// LRU last unfilled block
	};
}
